#include "webRelay.h"
using namespace yhaida;
WebRelay::WebRelay(EventLoop *loop,
                   const InetAddress &listenAddr,
                   const std::map<std::string, InetAddress> &backends,
                   int idleSeconds,
                   int numberTunnel)
    : _server(loop, listenAddr),
      _redirections(backends),
      _idleSeconds(idleSeconds),
      _numberTunnel(numberTunnel)
{
    _server.setThreadInitCallback(std::bind(&WebRelay::initPerThread, this, _1)); //初始化ThreadLocal
    _server.setConnectionCallback(
        std::bind(&WebRelay::onConnection, this, _1));
    _server.setMessageCallback(
        std::bind(&WebRelay::onMessage, this, _1, _2, _3));
}

void WebRelay::initPerThread(EventLoop *ioLoop)
{
    LOG_INFO << "WebRelay::initPerThread";
    assert(_t_backends::pointer() == NULL && _t_WeakConnectionList::pointer() == NULL);
    _t_backends::instance(); //在线程里面初始化ConnectionList
    _t_WeakConnectionList::instance().resize(_idleSeconds);
    assert(_t_backends::pointer() != NULL && _t_WeakConnectionList::pointer() != NULL);

    // ioLoop->runEvery(5.0, std::bind(&WebRelay::onTime, this));
    // dumpConnectionBuckets();

    for (auto &item : _redirections)
    {
        std::map<std::string, std::queue<TunnelPtr>> map;
        // TODO 开放10接口
        for (int j = 0; j < _numberTunnel; ++j)
        {
            TunnelPtr p(new Tunnel(ioLoop, item.second));
            p->setup();
            // p->connect();
            _t_FreeConnLists::instance()[item.first].emplace(p);
            // auto tunnel = _t_FreeConnLists::instance()[item.first].back();
            // tunnel->connect();
            _t_FreeConnLists::instance()[item.first].back()->connect(); //连接
        }
    }
    _t_KeepAliveConnLists::instance() = _t_FreeConnLists::instance();

    ioLoop->runEvery(5.0, std::bind(&WebRelay::onTime, this));
    dumpConnectionBuckets();
}

void WebRelay::onConnection(const TcpConnectionPtr &conn)
{
    LOG_INFO << "WebRelayServer - " << conn->peerAddress().toHostPort() << " -> "
             << conn->localhostAddress().toHostPort() << " is "
             << (conn->connected() ? "UP" : "DOWN");
    if (conn->connected())
    {
        NodePtr node(new Node(conn));
        _t_WeakConnectionList::instance().back().insert(node);

        dumpConnectionBuckets();

        WeakNodePtr weakNode(node);
        conn->setTimeContext(weakNode);

        //
        conn->setTcpNoDelay(true);
        //初始化http类
        conn->setHttpContext(HttpContext());
        conn->setContext(Type());
    }
    else
    {
        assert(!conn->getTimeContext().empty());
        assert(!conn->getContext().empty());
        WeakNodePtr weakNode(boost::any_cast<WeakNodePtr>(conn->getTimeContext()));
        LOG_DEBUG << "Node use_count = " << weakNode.use_count();

        // 超时后断开TCP连接
        Type type(boost::any_cast<Type>(conn->getContext())); //<-
        // auto it = _t_backends::instance().find(conn->name()+type._type);
        for (auto &item : type.getType())
        {
            auto it = _t_backends::instance().find(conn->peerAddress().toHostPort() + item); //
            if (it != _t_backends::instance().end())
            {
                // it->second->disconnect();
                it->second->reset();
                _t_FreeConnLists::instance()[it->second->getType()].push(it->second);
                LOG_DEBUG << "*****************" << it->second->getType() << "**********************************************************";
                // _t_FreeConnLists::instance()[type._type].push(it->second);
                _t_backends::instance().erase(it);
            }
        }
        LOG_DEBUG;
    }
}

void WebRelay::onMessage(const TcpConnectionPtr &conn,
                         Buffer *buffer,
                         Timestamp receiveTime)
{
    std::string sendMessage(buffer->peek(), buffer->readableBytes());

    assert(!conn->getTimeContext().empty());
    WeakNodePtr weakNode(boost::any_cast<WeakNodePtr>(conn->getTimeContext()));
    NodePtr node = weakNode.lock();
    if (node)
    {
        _t_WeakConnectionList::instance().back().insert(node);
        dumpConnectionBuckets();
    }

    //解析报文
    HttpContext *context = boost::any_cast<HttpContext>(conn->getMutableHttpContext());

    if (!context->parseRequest(buffer, receiveTime))
    {
        conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
        conn->shutdown();
    }

    if (context->gotAll())
    {
        HttpRequest &req = context->request(); //根据生成响应报文
        const std::string &connection = req.getHeader("Connection");
        bool close = connection == "close" ||
                     (req.getVersion() == HttpRequest::kHttp10 && connection != "Keep-Alive");

        auto it = _redirections.find(req.path());

        if (req.path() == "/0")
        {
            HttpResponse response(close);
            response.setStatusCode(HttpResponse::k301MovedPermanently);
            response.setStatusQuery("Moved Permanently");
            response.addHeader("Location", "https://www.baidu.com/");
            onRequest(conn, req, &response);
        }
        else if (req.path() == "/log")
        {
            LOG_DEBUG;
            Type *type = boost::any_cast<Type>(conn->getMutableContext());
            type->add("/log");
            TunnelPtr tunnel;
            //第一次
            if (_t_backends::instance().find(conn->peerAddress().toHostPort() + "/log") == _t_backends::instance().end())
            {
                if (_t_FreeConnLists::instance()["/log"].empty())
                {
                    LOG_DEBUG;
                    conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
                    conn->shutdown();
                    return;
                }
                else
                {
                    LOG_DEBUG;
                    conn->stopRead();
                    tunnel = _t_FreeConnLists::instance()["/log"].front();
                    _t_FreeConnLists::instance()["/log"].pop();
                    // tunnel->setup();
                    tunnel->setType("/log");
                    tunnel->setServerConn(conn);
                    _t_backends::instance()[conn->peerAddress().toHostPort() + "/log"] = tunnel; //存入map
                }
            }
            LOG_DEBUG;
            tunnel = _t_backends::instance()[conn->peerAddress().toHostPort() + "/log"];
            auto clientConn = tunnel->getClient();
            if (clientConn)
            {
                clientConn->send(sendMessage);
            }
            else
            {
                LOG_DEBUG;
            }
        }
        else if (req.path() == "/shudu") // fastcgi
        {
            LOG_DEBUG << "*****************************************************";
            Type *type = boost::any_cast<Type>(conn->getMutableContext());
            type->add("/shudu");

            TunnelPtr tunnel;
            if (_t_backends::instance().find(conn->peerAddress().toHostPort() + "/shudu") == _t_backends::instance().end())
            {
                if (_t_FreeConnLists::instance()["/shudu"].empty())
                {
                    conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
                    conn->shutdown();
                    return;
                }
                else
                {
                    LOG_DEBUG << "===========================================================================";
                    conn->stopRead();
                    tunnel = _t_FreeConnLists::instance()["/shudu"].front();
                    _t_FreeConnLists::instance()["/shudu"].pop();
                    // tunnel->setup();
                    tunnel->setType("/shudu");
                    tunnel->setServerConn(conn);
                    _t_backends::instance()[conn->peerAddress().toHostPort() + "/shudu"] = tunnel; //存入map
                }
            }
            LOG_DEBUG;
            tunnel = _t_backends::instance()[conn->peerAddress().toHostPort() + "/shudu"];
            LOG_DEBUG;
            auto clientConn = tunnel->getClient();
            LOG_DEBUG << clientConn->name();
            if (clientConn)
            {
                // req 请求
                // p12=0&p12=0&p13=0&p14=0&p15=0&p16=0&p17=0&p18=0&p19=0&p21=0&p22=0&p22=0&p23=0&p24=0&p25=0&p26=0&p27=0&p28=0&p29=0&p31=0&p32=0&p32=0&p33=0&p34=0&p35=0&p36
                // LOG_INFO<<req.getBody();
                std::string paramBody = req.getBody();
                std::string paramString("REQUEST_URI\r\n/sudoku/");
                size_t pos = 4;
                while (pos <= paramBody.size())
                {
                    paramString += paramBody.substr(pos, 1);
                    pos += 6;
                }
                paramString += "\r\n";
                fastcgiRequest(clientConn, paramString);
            }
            else
            {
                LOG_DEBUG;
            }
            LOG_DEBUG;
        }
        else if (req.path() == "/")
        {
            HttpResponse response(close);
            response.setStatusCode(HttpResponse::k200Ok);
            response.setStatusQuery("OK");
            response.setContentType("text/html");
            std::string nowTime = Timestamp::now().toFormattedString();
            std::string text = readFile("../html/log.html");
            response.setBody(text);
            onRequest(conn, req, &response);
        }
        else if (it == _redirections.end())
        {
            HttpResponse response(close);
            response.setStatusCode(HttpResponse::k200Ok);
            response.setStatusQuery("OK");
            response.setContentType("text/html");
            std::string nowTime = Timestamp::now().toFormattedString();
            string text("hello world!");
            response.setBody("<html><head><title>My tiny short url service</title></head>"
                             "<body><h1>Known redirections</h1>" +
                             text +
                             "Now is " + nowTime +
                             "</body></html>");
            onRequest(conn, req, &response);
        }
        else
        {
            HttpResponse response(close);
            response.setStatusCode(HttpResponse::k404NotFound);
            response.setStatusQuery("Not Found");
            response.setCloseConnection(true);

            onRequest(conn, req, &response);
        }

        context->reset();
    }
}
void WebRelay::fastcgiBegin(Buffer *message)
{
    Buffer requestBuffer;
    uint16_t role = sockets::hostToNetwork16(FastCgiCodec::FcgiRole::kFcgiResponder);
    uint8_t flags = FastCgiCodec::FcgiConstant::kFcgiKeepConn;
    requestBuffer.append(&role, sizeof(uint16_t));
    requestBuffer.append(&flags, sizeof(uint8_t));

    // 1.参数Param
    FastCgiCodec::RecordHeader requestParam;
    requestParam.version = 1;
    requestParam.type = FastCgiCodec::kFcgiBeginRequest;
    requestParam.id = sockets::hostToNetwork16(1);
    requestParam.length = sockets::hostToNetwork16(static_cast<uint16_t>(requestBuffer.readableBytes()));
    requestParam.padding = static_cast<uint8_t>(-requestBuffer.readableBytes() & 7);
    requestParam.unused = 0;
    message->prepend(&requestParam, FastCgiCodec::kRecordHeader);
    message->append(requestBuffer.retrieveAllAsString());
    message->append("\0\0\0\0\0\0\0\0", requestParam.padding);
}
void WebRelay::fastcgiParams(Buffer *message, const std::string &paramString)
{
    // 2.参数Param
    // std::string paramString("REQUEST_URI\r\n/sudoku/100000000020000000003000000000400000000050000000006000000000700000000080000000009\r\n");
    FastCgiCodec::RecordHeader headerParam;
    headerParam.version = 1;
    headerParam.type = FastCgiCodec::kFcgiParams;
    headerParam.id = sockets::hostToNetwork16(1);
    headerParam.length = sockets::hostToNetwork16(static_cast<uint16_t>(paramString.size()));
    headerParam.padding = static_cast<uint8_t>(-paramString.size() & 7);
    headerParam.unused = 0;

    message->append(&headerParam, FastCgiCodec::kRecordHeader);
    message->append(paramString);
    message->append("\0\0\0\0\0\0\0\0", headerParam.padding);

    //参数ParamEnd
    FastCgiCodec::RecordHeader headerParamEnd =
        {
            1,                           // version
            FastCgiCodec::kFcgiParams,   // type
            sockets::hostToNetwork16(1), // id
            0,                           // length
            0,                           // padding
            0,                           // unused
        };
    message->append(&headerParamEnd, FastCgiCodec::kRecordHeader);
}
void WebRelay::fastcgiStdin(Buffer *message)
{
    //参数ParamEnd
    FastCgiCodec::RecordHeader headerStdinEnd =
        {
            1,                           // version
            FastCgiCodec::kFcgiStdin,    // type
            sockets::hostToNetwork16(1), // id
            0,                           // length
            0,                           // padding
            0,                           // unused
        };
    message->append(&headerStdinEnd, FastCgiCodec::kRecordHeader);
}
void WebRelay::fastcgiRequest(const TcpConnectionPtr &conn, const std::string &param)
{
    Buffer message;
    fastcgiBegin(&message);
    fastcgiParams(&message, param);
    fastcgiStdin(&message);
    conn->send(&message);
}

void WebRelay::onRequest(const TcpConnectionPtr &conn, const HttpRequest &req, HttpResponse *resp)
{
    Buffer buffer;
    resp->appendToBuffer(&buffer);

    conn->send(&buffer);
    if (resp->closeConnection())
    {
        conn->shutdown();
    }
}

void WebRelay::onTime()
{
    _t_WeakConnectionList::instance().push_back(Bucket());
    dumpConnectionBuckets();
}

void WebRelay::dumpConnectionBuckets()
{
    LOG_INFO << "size = " << _t_WeakConnectionList::instance().size();
    int idx = 0;
    for (WeakConnectionList::const_iterator bucketI = _t_WeakConnectionList::instance().begin();
         bucketI != _t_WeakConnectionList::instance().end();
         ++bucketI, ++idx)
    {
        const Bucket &bucket = *bucketI;
        printf("[%d] len = %zd : ", idx, bucket.size());
        for (const auto &it : bucket)
        {
            bool connectionDead = it->_weakConn.expired();
            printf("%p(%ld)%s, ", yhaida::get_pointer(it), it.use_count(),
                   connectionDead ? " DEAD" : "");
        }
        puts("");
    }
}